﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.ServiceModel.Description;
using System.Text;
using System.Xml;
using System.Xml.XPath;

namespace WsOperations
{
    /// <summary>
    /// Helper class to aid in constructing a formatted string representation
    /// of a WSDL operation.
    /// </summary>
    class OperationWithParameters
    {
        /// <summary>
        /// The name of the operation
        /// </summary>
        private string name;
        /// <summary>
        /// The inputs to the operation
        /// </summary>
        private List<string> inputs = new List<string>();
        /// <summary>
        /// The output type of the operation
        /// </summary>
        private string output;

        /// <summary>
        /// Create a new operation with parameters
        /// </summary>
        /// <param name="name">the name of the operation</param>
        /// <param name="output">the output type of the operation</param>
        public OperationWithParameters(string name, string output)
        {
            this.name = name;
            this.output = output;
        }

        /// <summary>
        /// Add an input to the endpoint
        /// </summary>
        /// <param name="input">the input, either as a type on its own, or as
        /// "name:type"</param>
        public void AddInput(string input)
        {
            this.inputs.Add(input);
        }

        /// <summary>
        /// Format the operation as name(commaSeparatedInputList): returnType
        /// </summary>
        /// <returns>the formatted endpoint description</returns>
        public string ToResultString()
        {
            // create a comma separated list of inputs
            String inputStrings = String.Join(", ", this.inputs);
            // return the formatted operation as "operationName(commaSeparatedInputs): return type
            return String.Format("{0}({1}): {2}", this.name, inputStrings, this.output);
        }

        /// <summary>
        /// Factory method to create a full description of an endpoint with
        /// operations.
        /// </summary>
        /// <param name="navigator">a navigator at an wsdl:operation node</param>
        /// <param name="manager">an XML namespace manager aware of the wsdl
        /// namespace</param>
        /// <returns>the constructed description of an endpoint</returns>
        public static OperationWithParameters CreateFromXPathNavigator(XPathNavigator navigator, XmlNamespaceManager manager)
        {
            // select the name attribute of this node as the operation name
            string name = navigator.SelectSingleNode("./@name", manager).Value;
            // select the message attribute of the wsdl:output child as the operation return type
            string output = navigator.SelectSingleNode("./wsdl:output/@message", manager).Value;
            // construct the operation representation from the name and output type
            OperationWithParameters operation = new OperationWithParameters(name, output);
            // select all the message attributes of wsdl:input children as the input types
            XPathNodeIterator inputIterator = navigator.Select("./wsdl:input/@message", manager);
            // loop over the input messages
            while (inputIterator.MoveNext())
            {
                // Get the attribute value and add it to the operation
                string type = inputIterator.Current.Value;
                operation.AddInput(type);
            }
            // return the constructed operation representation
            return operation;
        }
    }

    /// <summary>
    /// Implementation of the WsOperations service.
    /// </summary>
    public class WsOperationsService : IWsOperationsService
    {
        /// <summary>
        /// Helper function that returns the formatted descriptions of the
        /// WSDL operations.
        /// </summary>
        /// <param name="navigator">an XPath navigator at the root of a document</param>
        /// <param name="manager">a namespace manager</param>
        /// <returns>an iterable list of the descriptiong of the operations</returns>
        private IEnumerable<string> DocumentEndpoints(XPathNavigator navigator, XmlNamespaceManager manager)
        {
            // Select all the operation definitions
            XPathNodeIterator iterator = navigator.Select("//wsdl:portType/wsdl:operation", manager);
            // Loop through all the operation definitions
            while (iterator.MoveNext())
            {
                // create a grouping on an endpoint with its inputs and outputs
                OperationWithParameters endpoint = OperationWithParameters.CreateFromXPathNavigator(iterator.Current, manager);
                // yield the result string
                yield return endpoint.ToResultString();
            }
        }

        public string[] getWsOperations(string url)
        {
            // Create a document and load the XML
            XmlDocument fullDocument = new XmlDocument();
            fullDocument.Load(url);
            // Build an XPath document out of the WSDL at the given URL
            XPathDocument doc = new XPathDocument(url);
            // Create a navigator to execute XPath expressions out of the current
            XPathNavigator navigator = doc.CreateNavigator();
            // Create an XML namespace manager
            XmlNamespaceManager manager = new XmlNamespaceManager(fullDocument.NameTable);
            manager.AddNamespace("wsdl", "http://schemas.xmlsoap.org/wsdl/");
            // Get the results of the operation queries
            List<string> results = new List<string>(this.DocumentEndpoints(navigator, manager));
            return results.ToArray();
        }
    }
}
